/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include <map>
#include <netinet/tcp.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>

#include <nghttp2/nghttp2.h>
#include <fcntl.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/conf.h>
#include <unordered_map>
#include "nlohmann/detail/input/input_adapters.hpp"
#include "nlohmann/json_fwd.hpp"
#include "ohos_log.h"
#include "http2_struct.h"
#include "napi/native_api.h"
#include "nlohmann/json.hpp"

enum { IO_NONE, WANT_READ, WANT_WRITE };

#define MAKE_NV(NAME, VALUE)                                                                                           \
    { (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, NGHTTP2_NV_FLAG_NONE }

#define MAKE_NV_CS(NAME, VALUE)                                                                                        \
    { (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, strlen(VALUE), NGHTTP2_NV_FLAG_NONE }

const int HTTPSTREAM_CALLBACK_TYPE_REPONSE = 1;
const int HTTPSTREAM_CALLBACK_TYPE_DATA_TRUNCK = 2;
const int HTTPSTREAM_CALLBACK_TYPE_CLOSE = 3;
const int HTTPSTREAM_CALLBACK_TYPE_END = 4;
const int HTTPSTREAM_CALLBACK_TYPE_ERROR = 5;
const int HTTPSTREAM_CALLBACK_TYPE_GOWAY = 6;

static std::unordered_map<int, Connection *> connectManagerMap;
static std::unordered_map<int, RequestTask *> requestManagerMap;
static std::unordered_map<char *, char *> responseHeadersManagerMap;
static std::unordered_map<nghttp2_session *, bool> needSetResponseHeaders;
static std::unordered_map<void *, void *> requestCallbackDataMap;
static std::unordered_map<int, bool> reqManagerMap;
static std::unordered_map<int, int> sessionManagerMap;
static std::unordered_map<int, bool> streamCloseManagerMap;
static std::unordered_map<int, bool> freeManagerMap;

static int stream_id_ = 0;
static std::string host_;
static std::string path_;
static std::string method_;
static std::string contentType_;
static int32_t code_ = NGHTTP2_NO_ERROR;
static bool isNeedRequest = false;

void (*post_event)(int type, void *data, int len, int reqType);

void message_callback(void (*postEvent)(int type, void *data, int len, int reqType)) { post_event = postEvent; }


#ifndef OPENSSL_NO_NEXTPROTONEG

static int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in,
                                unsigned int inlen, void *arg) {
    int rv;
    rv = nghttp2_select_next_protocol(out, outlen, in, inlen);
    if (rv <= 0) {
        LOGE("Server did not advertise HTTP/2 protocol");
    }
    return SSL_TLSEXT_ERR_OK;
}
#endif


static void init_ssl_ctx(SSL_CTX *ssl_ctx) {
    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
#ifndef OPENSSL_NO_NEXTPROTONEG
    SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
#endif

#if OPENSSL_VERSION_NUMBER >= 0x10002000L
    SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
#endif
}

static int ssl_handshake(SSL *ssl, int fd, std::string host) {
    const char *version = SSL_get_version(ssl);
    std::size_t hostLen = host.find(':');
    std::string domain = host.substr(0, hostLen);
    LOGE("ssl_handshake version %s host %s domain %s fd %d", version, host.c_str(),domain.c_str(),fd);
    int rv;
    if (SSL_set_fd(ssl, fd) == 0) {
        LOGE("SSL_set_fd %s", ERR_error_string(ERR_get_error(), NULL));
    }
    ERR_clear_error();
    SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void *)domain.c_str());
    rv = SSL_connect(ssl);
    if (rv <= 0) {
        LOGE("SSL_connect %s", ERR_error_string(ERR_get_error(), NULL));
    }
    LOGE("ssl_handshake rv %d", rv);
    return rv;
}

static void make_non_block(int fd) {
    int flags, rv;
    while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
        ;
    if (flags == -1) {
        LOGE("fcntl %s", strerror(errno));
    }
    while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
        ;
    if (rv == -1) {
        LOGE("fcntl end %s", strerror(errno));
    }
}

static void set_tcp_nodelay(int fd) {
    int val = 1;
    int rv;
    rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
    if (rv == -1) {
        LOGE("setsockopt %s", strerror(errno));
    }
}


bool is_destroyed(int fd) {
    bool result = false;
    auto it = sessionManagerMap.find(fd);
    if (it != sessionManagerMap.end()) {
        for (const auto &child : sessionManagerMap) {
            if ((child.first == fd) && (child.second == 1)) {
                result = true;
                break;
            }
        }
    }
    return result;
}

static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) {
    LOGE("send_callback");
    struct Connection *connection;
    int rv;
    connection = (struct Connection *)user_data;
    connection->want_io = IO_NONE;
    connection->stream_id = stream_id_;
    LOGE("send_callback fd:%d", connection->fd);
    if (is_destroyed(connection->fd))
        return 0;
    ERR_clear_error();
    rv = SSL_write(connection->ssl, data, (int)length);
    if (rv <= 0) {
        int err = SSL_get_error(connection->ssl, rv);
        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
            connection->want_io = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
            rv = NGHTTP2_ERR_WOULDBLOCK;
        } else {
            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
        }
    }
    return rv;
}

static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *user_data) {
    LOGE("recv_callback %d", length);
    int rv;
    struct Connection *connection;
    connection = (struct Connection *)user_data;
    connection->want_io = IO_NONE;
    if ((connection == NULL) || (connection->ssl == NULL) || (buf == NULL) || is_destroyed(connection->fd))
        return 0;
    ERR_clear_error();
    rv = SSL_read(connection->ssl, buf, (int)length);
    LOGE("recv_callback SSL_read %d", rv);
    if (rv < 0) {
        int err = SSL_get_error(connection->ssl, rv);
        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
            connection->want_io = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
            rv = NGHTTP2_ERR_WOULDBLOCK;
        } else {
            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
        }
    } else if (rv == 0) {
        rv = NGHTTP2_ERR_EOF;
        LOGE("recv_callback NGHTTP2_ERR_EOF");
    }
    return rv;
}


void HandleGoawayFrame(void *user_data, nghttp2_goaway goway) {
    struct Connection *connection = (Connection *)user_data;
    LOGE("HandleGoawayFrame fd:%d data:%s", connection->fd, (char *)goway.opaque_data);
    std::unordered_map<char *, void *> map;
    map["opaque_data"] = goway.opaque_data;
    map["error_code"] = (int *)goway.error_code;
    map["last_stream_id"] = (int *)goway.last_stream_id;
    map["fd"] = (int *)connection->fd;
    post_event(HTTPSTREAM_CALLBACK_TYPE_GOWAY, &map, 0, connection->reqType);
}

static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) {
    LOGE("on_frame_recv_callback %d", frame->hd.type);
    struct Connection *connection;
    connection = (struct Connection *)user_data;
    if (is_destroyed(connection->fd))
        return 0;
    switch (frame->hd.type) {
    case NGHTTP2_GOAWAY:
        LOGE("on_frame_recv_callback NGHTTP2_GOAWAY");
        HandleGoawayFrame(user_data, frame->goaway);
        break;
    default:
        break;
    }
    return 0;
}


static std::string unorderedMapToJsonString(const std::unordered_map<char *, char *> &myMap) {
    nlohmann::json json = myMap;
    return json.dump();
}


static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) {
    LOGE("on_stream_close_callback errcode %d", error_code);
    const std::string map = unorderedMapToJsonString(responseHeadersManagerMap);
    LOGE("on_stream_close_callback map %s", map.c_str());
    struct Connection *connection;
    connection = (struct Connection *)user_data;
    post_event(HTTPSTREAM_CALLBACK_TYPE_REPONSE, (void *)((char *)map.c_str()), 0, connection->reqType);
    responseHeadersManagerMap.clear();
    usleep(100);
    post_event(HTTPSTREAM_CALLBACK_TYPE_CLOSE, (char *)std::to_string(error_code).c_str(), 0, connection->reqType);
    usleep(100);
    post_event(HTTPSTREAM_CALLBACK_TYPE_END, NULL, 0, connection->reqType);
    streamCloseManagerMap[connection->fd] = true;
    return 0;
}

static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data,
                                       size_t len, void *user_data) {

    LOGE("on_data_chunk_recv_callback len:%d", len);
    struct Connection *connection;
    connection = (struct Connection *)user_data;
    if (is_destroyed(connection->fd))
        return 0;
    LOGE("on_data_chunk_recv_callback reqType %d", connection->reqType);
//     for (int i = 0; i < len; i++) {
//         LOGE("on_data_chunk_recv_callback data  %d", data[i]);
//     }
    post_event(HTTPSTREAM_CALLBACK_TYPE_DATA_TRUNCK, (void *)data, len, connection->reqType);
    return 0;
}

static int OnHeaderCallback(nghttp2_session *handle, const nghttp2_frame *frame, nghttp2_rcbuf *name,
                            nghttp2_rcbuf *value, uint8_t flags, void *user_data) {
    LOGE("OnHeaderCallback name %s value %s flags %d", nghttp2_rcbuf_get_buf(name).base,
         nghttp2_rcbuf_get_buf(value).base, flags);
    struct Connection *connection;
    connection = (struct Connection *)user_data;
    if (is_destroyed(connection->fd))
        return 0;
    auto isNeed = needSetResponseHeaders.find(handle);
    if (isNeed == needSetResponseHeaders.end()) {
        responseHeadersManagerMap[(char *)nghttp2_rcbuf_get_buf(name).base] = (char *)nghttp2_rcbuf_get_buf(value).base;
    }
    return 0;
}


static int OnNghttpError(nghttp2_session *handle, int lib_error_code, const char *message, size_t len,
                         void *user_data) {
    struct Connection *connection;
    connection = (struct Connection *)user_data;
    if (is_destroyed(connection->fd))
        return 0;
    LOGE("OnNghttpError errcode %d msg %s", lib_error_code, message);
    HttpStreamErrorCallback errCallBack;
    errCallBack.errCode = lib_error_code;
    errCallBack.msg = message;
    static std::unordered_map<char *, char *> map;
    map["message"] = (char *)message;
    map["code"] = (char *)std::to_string(lib_error_code).c_str();
    std::string result = unorderedMapToJsonString(map);
    post_event(HTTPSTREAM_CALLBACK_TYPE_ERROR, (void *)result.c_str(), 0, connection->reqType);
    streamCloseManagerMap[connection->fd] = true;
    return 0;
}

static void setup_nghttp2_callbacks(nghttp2_session_callbacks *callbacks) {
    nghttp2_session_callbacks_set_on_header_callback2(callbacks, OnHeaderCallback);
    nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
    nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);
    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback);
    nghttp2_session_callbacks_set_error_callback2(callbacks, OnNghttpError);
}


static void submit_request(Connection *connection, std::string host, std::string path, std::string method,
                           std::string contentType) {
    host_ = host;
    path_ = path;
    method_ = method;
    contentType_ = contentType;
    LOGE("submit_request path:%s host:%s method:%s contentType:%s", path.c_str(), host.c_str(), method.c_str(),
         contentType.c_str());
    // TODO grpc请求不走这里 拦截
    if (contentType != "application/json")
        return;
    // TODO MAKE_NV_CS 是动态传变量
    const nghttp2_nv nva[] = {MAKE_NV_CS(":method", method.c_str()),
                              MAKE_NV_CS(":path", path.c_str()),
                              MAKE_NV(":scheme", "https"),
                              MAKE_NV_CS(":authority", host.c_str()),
                              MAKE_NV("accept", "*/*"),
                              MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)};
    connection->reqType = 0;
    stream_id_ = nghttp2_submit_request(connection->session, NULL, nva, sizeof(nva) / sizeof(nva[0]), NULL, connection);
    if (stream_id_ < 0) {
        LOGE("nghttp2_submit_request %d", stream_id_);
    }
}


static void ctl_poll(struct pollfd *pollfd, nghttp2_session *session, int want_io) {
    pollfd->events = 0;
    if (nghttp2_session_want_read(session) || want_io == WANT_READ) {
        pollfd->events |= POLLIN;
    }
    if (nghttp2_session_want_write(session) || want_io == WANT_WRITE) {
        pollfd->events |= POLLOUT;
    }
}


static int handle_io(nghttp2_session *session, int fd) {
    LOGE("handle_io");
    if (!session || is_destroyed(fd))
        return 0;
    int rv;
    rv = nghttp2_session_recv(session);
    if (rv != 0) {
        LOGE("nghttp2_session_recv %d", rv);
        return rv;
    }
    rv = nghttp2_session_send(session);
    if (rv != 0) {
        LOGE("nghttp2_session_send %d", rv);
    }
    return rv;
}


static ssize_t data_source_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len,
                                         uint32_t *data_flags, nghttp2_data_source *source, void *user_data) {
    LOGE("data_source_read_callback start");
    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
    uint8_t *bufferData = static_cast<uint8_t *>(source->ptr);
    Connection *connection = (struct Connection *)user_data;
    LOGE("data_source_read_callback write_buff_len %d", connection->write_buff_len);
    std::memcpy(buf, bufferData, connection->write_buff_len);
    return connection->write_buff_len;
}


static void sendData(Connection *connect, void *buff, int buffLen) {
    LOGE("napi--->sendData fd %d host %s path %s buffLen %d", connect->fd, connect->host.c_str(), connect->path.c_str(),
         buffLen);
    const nghttp2_nv nva[] = {MAKE_NV(":method", "POST"),
                              MAKE_NV_CS(":path", connect->path.c_str()),
                              MAKE_NV(":scheme", "https"),
                              MAKE_NV_CS(":authority", connect->host.c_str()),
                              MAKE_NV("content-type", "application/grpc"),
                              MAKE_NV("accept", "*/*"),
                              MAKE_NV("te", "trailers"),
                              MAKE_NV("accept-encoding", "identity"),
                              MAKE_NV("grpc-accept-encoding", "identity"),
                              MAKE_NV("user-agent", "grpc-node-js/0.1.0" NGHTTP2_VERSION)};

    nghttp2_data_source reqBuffer;
    reqBuffer.ptr = buff;
    nghttp2_data_provider data_prvd = {reqBuffer, data_source_read_callback};
    connect->write_buff_len = buffLen;
    connect->reqType = 1;
    stream_id_ = nghttp2_submit_request(connect->session, NULL, nva, sizeof(nva) / sizeof(nva[0]), &data_prvd, connect);
    if (stream_id_ < 0) {
        LOGE("sendData %d", stream_id_);
    }
}

bool createArrayBuffer(napi_env env, void *src, size_t srcLen, napi_value *res) {
    if (src == nullptr || srcLen == 0) {
        return false;
    }
    void *nativePtr = nullptr;
    if (napi_create_arraybuffer(env, srcLen, &nativePtr, res) != napi_ok || nativePtr == nullptr) {
        return false;
    }
    memcpy(nativePtr, src, srcLen);
    return true;
}

HandleBuffer checkBufferNum(uint8_t *buffer, int len) {
    struct HandleBuffer handleBuffer;
    for (int i = 0; i < len; i++) {
        if ((buffer[i] > 31) && (buffer[i] <= 126)) {
            handleBuffer.dirty_data_Len = i;
            handleBuffer.effective_data_len = len - i;
            break;
        }
    }
    return handleBuffer;
}
